/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ipc_endpoint_service.cpp
 *
 * Purpose: implementation a service endpoint with ipc
 *
 * Developer:
 *   wen.gu , 2021-10-12
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "ipc_endpoint_service.h"


#define LOG_TAG "ipes"
#include "icpp/core/log.h"

namespace icpp
{
namespace com
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define IPC_SRV_COMP_NAME "ipc_endpoint_service"
#define IPC_SRV_COMP_VER  "0.0.1"
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

class MyMessageOpaque: public MessageOpaque
{
public:
    MyMessageOpaque(int socket_id)
    {
        socket_id_ = socket_id;
    }
public:
    int socket_id_ = INVALID_SOCKET_CLIENT_ID;
}

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static bool GetClientIdBySocketId(IpcEndpointService::SocketMap2ClientId& map_info, int socket_id, ClientId& client_id)
{
    IpcEndpointService::SocketMap2ClientId::iterator it = map_info.find(socket_id);
    if (it != map_info.end())
    {
        client_id = it->second;
        return true;
    }

    return false;
}


static bool GetSocketIdByClientId(IpcEndpointService::ClientIdMap2Socket& map_info, ClientId client_id, int& socket_id)
{
    IpcEndpointService::ClientIdMap2Socket::iterator it = map_info.find(client_id);
    if (it != map_info.end())
    {
        socket_id = it->second;
        return true;
    }

    return false;
}



/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

IpcEndpointService::IpcEndpointService(const std::string& url)
    :Endpoint(IPC_SRV_COMP_NAME, IPC_SRV_COMP_VER),
    url_(url),
    ipc_service_(url)
{
    /** todo something */
}

IpcEndpointService::~IpcEndpointService()
{
    /** todo something */
    stop();
}

/**initialize endpoint. 
 * if role service, then  do  communication protocol bind(e.g. socket bind)
 * if role client, then try to connect to service endpoint
 */
core::IcppErrc IpcEndpointService::start()
{
    return ipc_service_.start([this](int socket_client_id, const uint8_t* buf, uint32_t len) /** OnReceiveHandler */
    {
        IpcPacketParser::IpcPacketParserPtr parser = this->findPacketParser(socket_client_id);

        if (parser)
        {
            parser->fillData(buf, len);
        }
    },
    [this](int client_id, const std::string& url_addr) /** OnAddClientHandler */
    {
        this->onAddClient(client_id, url_addr);
    },
    [this](int client_id) /** OnDelClientHandler */
    {
        this->onDelClient(client_id);
    }
    );

    //return core::IcppErrc::NotImplemented;
}

/** uninitialize endpoint
 * if role service, then do unbind
 * if role client,  then disconect from service endpoint
*/
core::IcppErrc IpcEndpointService::stop()
{
    return ipc_service_.stop();
    //return core::IcppErrc::NotImplemented;
}

/** the common api */
/**
 * endpoint_id:   == INVALID_ENDPOINT_ID:   (default)broadcast to all endpoints which connected with current endpoint, 
 *                != INVALID_ENDPOINT_ID: send to a special endpoint  which connected with current endpoint
 */
core::IcppErrc IpcEndpointService::send(Message::MessagePtr msg_ptr)
{
    if (!msg_ptr)
    {
        return core::IcppErrc::BadParameter;
    }

    PayloadPtr payload_ptr = IpcPacketBuilder::MakeIpcPacket(msg_ptr);

    if (!payload_ptr)
    {
        return core::IcppErrc::BadParameter;
    }

    int socket_client_id = -1;  /** default as broad cast */

    if (INVALID_CLIENT_ID != msg_ptr->client_id())
    {
        if (!GetSocketIdByClientId(client_id_2_socket_, msg_ptr->client_id(), socket_client_id))
        {
            return core::IcppErrc::NotFound;
        }
    }

    return ipc_service_.send(socket_client_id, payload_ptr->data(), payload_ptr->size());
}

void IpcEndpointService::setReceiveHandler(ReceiveHandler handler)
{
    on_receive_ = handler;
}

void IpcEndpointService::setConnectionStateHandler(ConnectionStateHandler handler)
{
    on_connect_state_change_ = handler;
}

void IpcEndpointService::setEndpointIdAllocator(EndpointIdAllocator handler)
{
    on_endpoint_id_allocator_ = handler;
}

EndpointRole IpcEndpointService::role() const
{
    return EndpointRole::kService;
}

//EndpointId endpoint_id()
const std::string& IpcEndpointService::url() const 
{
    return url_;
}


IpcPacketParser::IpcPacketParserPtr IpcEndpointService::findPacketParser(int socket_id)
{
    ClientId client_id;

    if (GetClientIdBySocketId(socket_2_client_id_, socket_id, client_id))
    {
        ClientInfoMap::iterator it = client_infos_.find(client_id);

        if (it != client_infos_.end())
        {
            return it->second.parser_;
        }
    }    

    return nullptr;
}

void IpcEndpointService::onAddClient(int socket_client_id, const std::string& client_url)
{
    if (on_endpoint_id_allocator_)
    {
        ClientId client_id = on_endpoint_id_allocator_();
        socket_2_client_id_[socket_client_id] = client_id;
        client_id_2_socket_[client_id] = socket_client_id;
        ClientInfo ci;
        ReceiveHandler on_receive = on_receive_;

        ci.parser_ = std::make_shared<IpcPacketParser>([on_receive, socket_client_id](Message::MessagePtr msg_ptr)
        {
            MessageOpaque::MessageOpaquePtr opaque_ptr = std::make_shared<MyMessageOpaque>(socket_client_id);
            msg_ptr->set_opaque(opaque_ptr);
            on_receive(msg_ptr);
        }, client_id);
        //add ci.builder_
        client_infos_[client_id] = ci;
    }
}

void IpcEndpointService::onDelClient(int socket_client_id)
{
    SocketMap2ClientId::iterator it = socket_2_client_id_.find(socket_client_id);

    if (it != socket_2_client_id_.end())
    {
        ClientId client_id = it->second;
        ClientIdMap2Socket::iterator client_it = client_id_2_socket_.find(client_id);

        if (client_it != client_id_2_socket_.end())
        {
            client_id_2_socket_.erase(client_it);
        }

        ClientInfoMap::iterator info_it = client_infos_.find(client_id);
        if (info_it != client_infos_.end())
        {
            client_infos_.erase(info_it);
        }

        socket_2_client_id_.erase(it);
    }
}

} /** namespace com */
} /** namespace icpp */
